/**
 * DS1302 实时时钟(时钟芯片) & 可调时钟
 *  DS1302是由美国DALLAS公司推出的具有涓细电流充电能力的低功耗实时时钟芯片。
 *      它可以对年、月、日、周、时、分、秒进行计时，且具有闰年补偿等多种功能
 *      RTC(Real Time Clock)：实时时钟，是一种集成电路，通常称为时钟芯片
 * 共8个引脚:
 *  GND:  接地
 *  Vcc1: 备用电池,可充电类型 (需自己外接)
 *  Vcc2: 主电源
 *  X1,X2: 接32.768KHz晶振 (需自己外接)
 *  CE:    使能, 高电平有效
 *  IO:    数据输入/输出
 *  SCLK:  串行时钟 (上升沿写命令 & 上升沿写, 下降沿读)
 * 
 * DS12C887
 *      内部有电池, 掉电可继续计时:
 *          https://www.bilibili.com/video/BV1dD4y1X7Vo/?vd_source=eec92494e7d9882d1d896c25dff0578a
 *          TX-1C的时钟芯片需要连线: 0:56:58
 *
 * DS3231 精度高, 但更贵
 * 
 * BCD码（Binary Coded Decimal‎），用4位二进制数来表示1位十进制数
 *  例：0001 0011表示13，1000 0101表示85，0001 1010不合法
 *      在十六进制中的体现：0x13表示13，0x85表示85，0x1A不合法
 *      BCD码转十进制：DEC=BCD/16*10+BCD%16; （2位BCD）
 *      十进制转BCD码：BCD=DEC/10*16+DEC%10; （2位BCD）
 * */
#include <REGX52.H>

sbit DS_1302_CE = P3^5;     //第5引脚, 使能
sbit DS_1302_IO = P3^4;     //第6引脚, 输入输出
sbit DS_1302_SCLK = P3^6;   //第7引脚, 串行时钟

//DS1302 的读命令
static code unsigned char DS1302_Read_Commons[] = {0x8D, 0x89, 0x87, 0x85, 0x83, 0x81, 0x8B};
//DS1302 的写命令
static code unsigned char DS1302_Write_Commons[] = {0x8C, 0x88, 0x86, 0x84, 0x82, 0x80, 0x8A};

/**
 * BCD码转十进制：DEC=BCD/16*10+BCD%16; （2位BCD）
 * @param BCD_Code BCD码
 * @return 返回10进制
 */
unsigned char BCD_Code_2_Decimal(unsigned char BCD_Code) {
    return BCD_Code / 16 * 10 + BCD_Code % 16;
}

/**
 * 十进制转BCD码：BCD=DEC/10*16+DEC%10; （2位BCD）
 * @param Decimal_Code 10进制码
 * @return 返回BCD码
 */
unsigned char Decimal_2_BCD_Code(unsigned char Decimal_Code) {
    return Decimal_Code / 10 * 16 + Decimal_Code % 10;
}

/**
 * 写入命令
 */
void DS1302_Write_Common(unsigned char Common) {
    static unsigned char i;
    DS_1302_CE = 1;
    DS_1302_SCLK = 0;
    for (i = 0; i < 8; i++) {
        DS_1302_IO = Common & (1 << i);
        DS_1302_SCLK = 1;
        //可直接置0, 不用加延时. 51单片机操作速度微秒级, 手册是50~200ns
        DS_1302_SCLK = 0;
    }
    //将数据 写入/读出 后, 再关闭使能
    // DS_1302_CE = 0;
}

/**
 * WP: write protect, 写保护, 倒数第2个寄存器. 0才能写数据, 否则只能读数据
 * @param writeAble 是否能写入数据
 */
void DS1302_Write_Able(unsigned char writeAble) {
    DS1302_Write_Common(0x8E);
    if (writeAble) {
        //关掉WP, 才能写进去
        DS1302_Write_Common(0x00);
    } else {
        DS1302_Write_Common(0x80);
    }
}

/**
 * 读取时钟
 * @param commFlag 时间flag, 0年, 1星期, 2月, 3日, 4时, 5分, 6秒, 7星期几
 * @return 返回10进制结果
 */
unsigned char DS1302_ReadByte(unsigned char commFlag) {
    static unsigned char i;
    unsigned char Data = 0;
    //写入'读'的命令
    DS1302_Write_Common(DS1302_Read_Commons[commFlag]);
    //读取数据()
    // DS_1302_SCLK = 0;
    for (i = 0; i < 8; i++) {
        //从最低位开始读
        //视频的, 感觉不合理, 00:29:50
        if (DS_1302_IO) Data = Data | (1 << i);
        DS_1302_SCLK = 1;
        //可直接置0, 不用加延时. 51单片机操作速度微秒级, 手册是50~200ns
        DS_1302_SCLK = 0;
    }
    //关闭使能
    DS_1302_CE = 0;
    //返回的 Data 是BCD码, 转换成10进制
    return BCD_Code_2_Decimal(Data);
}

/**
 * 设置时钟
 * @param commFlag 时间flag, 0年, 1星期, 2月, 3日, 4时, 5分, 6秒, 7星期几
 * @param Data 写入的数据, 10进制!!!
 */
void DS1302_WriteByte(unsigned char commFlag, unsigned char Data) {
    //写入'写'的命令
    DS1302_Write_Common(DS1302_Write_Commons[commFlag]);
    //写入数据, 要写入BCD码
    DS1302_Write_Common(Decimal_2_BCD_Code(Data));
    //关闭使能
    DS_1302_CE = 0;
}

/**
 * @brief 设置时间，调用之后，DS1302_Times[7] 数组中的数字(10进制)会被设置到DS1302中
 * @param DS1302_Times 索引0~6分别为年、月、日、时、分、秒、星期(10进制)，设置为有符号的便于 < 0的判断，示例:
 *                     char DS1302_Times[7] = {23, 2, 17, 12, 11, 37, 6};
 */
void DS1302_SetTime(char DS1302_Times[7]) {
	DS1302_WriteByte(0, DS1302_Times[0]);
	DS1302_WriteByte(1, DS1302_Times[1]);
	DS1302_WriteByte(2, DS1302_Times[2]);
	DS1302_WriteByte(3, DS1302_Times[3]);
	DS1302_WriteByte(4, DS1302_Times[4]);
	DS1302_WriteByte(5, DS1302_Times[5]);
	DS1302_WriteByte(6, DS1302_Times[6]);
}

/**
 * @brief  DS1302读取时间，调用之后，DS1302中的数据会被读取到 DS1302_Times[7] 数组中
 * @param DS1302_Times 索引0~6分别为年、月、日、时、分、秒、星期(10进制)，设置为有符号的便于 < 0的判断
 */
void DS1302_ReadTime(char DS1302_Times[7]) {
	DS1302_Times[0] = DS1302_ReadByte(0);
	DS1302_Times[1] = DS1302_ReadByte(1);
	DS1302_Times[2] = DS1302_ReadByte(2);
	DS1302_Times[3] = DS1302_ReadByte(3);
	DS1302_Times[4] = DS1302_ReadByte(4);
	DS1302_Times[5] = DS1302_ReadByte(5);
	DS1302_Times[6] = DS1302_ReadByte(6);
}

/**
 * 判断数组日期时间的范围是否正确
 * @param DS1302_Times 索引0~6分别为年、月、日、时、分、秒、星期(10进制)，设置为有符号的便于 < 0的判断，示例:
 *                     char DS1302_Times[7] = {23, 2, 17, 12, 11, 37, 6};
 * 其实日期设置后不用判断, DS1302 会自己判断, 读出来即可
 */
void judgeDateTime(char DS1302_Times[7]) {
    char year = DS1302_Times[0], month = DS1302_Times[1], day = DS1302_Times[2];
    //年越界判断(芯片手册: 0~99)
    if(year > 99) {
        year = DS1302_Times[0] = 0;
    } else if (year < 0) {
        year = DS1302_Times[0] = 99;
    }
    //月越界判断(1~12)
    if(month <= 0) {
        month = DS1302_Times[1] = 12;
    } else if (month > 12) {
        month = DS1302_Times[1] = 1;
    }
    //日越界判断(1~31)
    if (day <= 0) {
        day = DS1302_Times[2] = 31;
    }
    if(month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12) {
        //大月
        if(day > 31) { day = DS1302_Times[2] = 1; }
    } else if(month == 4 || month == 6 || month == 9 || month == 11) {
        //小月
        if(day > 30){ day = DS1302_Times[2] = 1; }
    } else if(month == 2) {
        /**
         * 1.如果某年份能被4整除且不能被100整除，那么这个年份为闰年。
         * 2.如果某年份能被400整除，那么这个年份是闰年
        */
        if((year % 4 == 0 && year % 100) ||  year % 400 == 0) {
            //闰年2月
            if(day > 29) { day = DS1302_Times[2] = 1; }
        } else if(day > 28) {
            //平年2月
            day = DS1302_Times[2] = 1;
        }
    }
    //时越界判断
    if(DS1302_Times[3] > 23) {
        DS1302_Times[3] = 0;
    } else if (DS1302_Times[3] < 0) {
        DS1302_Times[3] = 23;
    }
    //分越界判断
    if(DS1302_Times[4] > 59) {
        DS1302_Times[4] = 0;
    } else if (DS1302_Times[4] < 0) {
        DS1302_Times[4] = 59;
    }
    //秒越界判断
    if(DS1302_Times[5] > 59) {
        DS1302_Times[5] = 0;
    } else if (DS1302_Times[5] < 0) {
        DS1302_Times[5] = 59;
    }
}